/*
** banco.c
** va creando hijos por cada conexión que llega y por cada petición responde OK
** conforme su saldo o SIN_SALDO en caso de que no disponga de saldo suficiente
** para la acreditación
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <sqlite3.h>

#define PORTBANK "3701"  // the port users will be connecting to

#define BACKLOG 10	 // how many pending connections queue will hold
#define MAXDATASIZE 999

void sigchld_handler(int s)//manejador de senhal del hijo
{
	while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
void EnteroACadena(char* cadena, int n){
	char base[10]={'0','1','2','3','4','5','6','7','8','9'};
	int nCifras=1,a=10,i;
	while(n/a>0){nCifras++;a*=10;}
	a/=10;
	for(i=0;i<nCifras;i++,a/=10){
		cadena[i]=base[n/a];
		n%=a;	
	}
	cadena[nCifras]='\0';
}

/*-------------funcion de recuperacion de registro de bd------------------*/
char dato_monto[10]={"-1"};
int get_registro(void* paque,int n, char** re,char** nomcol){   
	strcpy(dato_monto,re[0]);
    return 0;
}
/*-------------funcion de query select para sqlite3-----------------------*/
char q_select[59];
void select_query(char pin[4], char nro_tarj[5]){
	strcat(q_select,"select monto from banco where pin=");
	strcat(q_select,pin);
	strcat(q_select," and num_tarj=");
	strcat(q_select,nro_tarj);
	strcat(q_select,";");
}
/*-------------funcion de query update para sqlite3-----------------------*/
char q_update[65];
void update_query(char pin[4], char nro_tarj[5], char new_monto[6]){
	strcat(q_update,"update banco set monto=");
	strcat(q_update,new_monto);
	strcat(q_update," where pin=");
	strcat(q_update,pin);
	strcat(q_update," and num_tarj=");
	strcat(q_update,nro_tarj);
	strcat(q_update,";");
}
/*-------------funcion de validacion de descuento y ROLLBACK-------------------------*/
int descontar(int monto, int descuento, char *option){
	if(option!=NULL){
		if(strcmp(option,"ROLLBACK")==0)
			return (monto+descuento);
	}else{
		if(monto < descuento)return -1;else return (monto-descuento);
	}
}

int main(void)
{
	int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage their_addr; // connector's address information
	socklen_t sin_size;
	struct sigaction sa;
	int yes=1;
	char s[INET6_ADDRSTRLEN];
	char buf[1000] = {'\0'}; 
	int rv;
	
	sqlite3* db;        	
	
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE; // use my IP
	//obtiene la direccion 
	if ((rv = getaddrinfo(NULL, PORTBANK, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
			perror("banco: socket");
			continue;
		}

		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("banco: bind");
			continue;
		}

		break;
	}

	if (p == NULL)  {
		fprintf(stderr, "server: failed to bind\n");
		return 2;
	}

	freeaddrinfo(servinfo); // all done with this structure

	if (listen(sockfd, BACKLOG) == -1) {
		perror("listen");
		exit(1);
	}

	sa.sa_handler = sigchld_handler; // reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
		perror("sigaction");
		exit(1);
	}

	printf("BANCO: Esperando conexiones...\n");

	while(1) {  // main accept() loop
		sin_size = sizeof their_addr;
		new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
		if (new_fd == -1) {
			perror("accept");
			continue;
		}

		inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof(s));
		printf("BANCO: Hay conexion desde  %s\n", s);

		if (!fork()) { // this is the child process
			close(sockfd); // child doesn't need the listener

			if ((rv = recv(new_fd, buf, MAXDATASIZE-1, 0)) == -1){
				perror("recv");
				exit(1);
			}
/*----------------variables para usar con la bd-----------------------------------*/			
			char nuevo_monto[10];			  			 //para cargarse en la bd
			char *card, *nro_pin, *desc_monto, *rollback;//datos recibidos del servidor
			int r_desc;
			
			card=strtok(buf,"\n");
			nro_pin=strtok(NULL,"\n");	
			desc_monto=strtok(NULL,"\n");
			rollback=strtok(NULL,"\n");

/*-------------------verificar cliente y descontar------------------------------*/	
			sqlite3_open("bdbanco.db",&db); 
			/*---------Comprobamos errores---------------*/
			if (sqlite3_errcode(db)!=0)
				printf("%s\n",sqlite3_errmsg(db));        
			else
			{ 				
				select_query(nro_pin,card);
				sqlite3_exec(db,q_select,get_registro,NULL,NULL);
					
				if(atoi(dato_monto)!=-1){
				
					if (sqlite3_errcode(db)!=0){ 
						printf("%s\n",sqlite3_errmsg(db));            
					}
					
					r_desc = descontar(atoi(dato_monto),atoi(desc_monto), rollback);
					
					EnteroACadena(nuevo_monto, r_desc);
					
					if(r_desc!=-1){
						update_query(nro_pin,card,nuevo_monto);
						sqlite3_exec(db,q_update,NULL,NULL,NULL);
				/*---------Comprobamos errores---------------*/
						if (sqlite3_errcode(db)!=0){ 
							printf("%s\n",sqlite3_errmsg(db));            
						}
					}
				
					sqlite3_close(db); 			
/*-------------------fin verificar cliente y decontar------------------------------*/
					if(rollback==NULL){
						if (r_desc != -1) { //Tiene saldo el cliente
							printf("\tOK debitado %dGs. de tarjeta nro %s\n",atoi(desc_monto),card);
							if (send(new_fd, "OK", strlen("OK"), 0) == -1)
								perror("send en el hijo");
						}else { //No tiene saldo el cliente
							printf("\tNO tiene saldo suficiente tarjeta nro %s\n", card);
							if (send(new_fd, "SIN_SALDO", strlen("SIN_SALDO"), 0) == -1)
								perror("send en el hijo");
						}
					}else{//Rollback del cliente
						printf("\tROLLBACK OK: tarj_nro=%s, monto=%sGs.\n", card, desc_monto);
						if (send(new_fd, "OK", strlen("OK"), 0) == -1)
							perror("send en el hijo");
					}
				}else{//No existe el cliente
					printf("\tCliente Inexistente nro:%s\n", card);
					if (send(new_fd, "CARD_FAIL", strlen("CARD_FAIL"), 0) == -1)
							perror("send en el hijo");
				}
				close(new_fd);
				exit(0);
			}
		}
		close(new_fd);  // parent doesn't need this
	}

	return 0;
}

